;;;;=========================================================================
;;;;=========================================================================
;;;;
;;;; This file includes the main routines for running the lisp search and
;;;; rescue client, and stub placeholders for the functions that need to
;;;; be overridden to implement the client.
;;;;
;;;; To override the initialize(), process-result() and choose-action()
;;;; functions, create a new file defining functions of the same name
;;;; and have lisp load this new file after this current file.
;;;;
;;;; DO NOT MODIFY THE CONTENTS OF THIS FILE -- it may be overwritten in 
;;;; later releases of the Search and Rescue simulator.
;;;;
;;;; Authors: Eric Eaton, Aaron Curtis
;;;;
;;;;=========================================================================
;;;;=========================================================================



;;;; Function: initialize
;;;;
;;;; This function is called automatically to allow the
;;;; agent to initialize itself.  The initial map has
;;;; already been initialized when this function is called.
;;;;
(defun initialize ()
  'OVERRIDE_THIS_FUNCTION)



;;;; Function: process-result
;;;;
;;;; This function allows the agent to process its final score.
;;;;
;;;; Arguments: score - the agent's final score
;;;;
;;;; Returns:  none
(defun process-result (score)
  'OVERRIDE_THIS_FUNCTION)



;;;; Function: choose-action
;;;;
;;;; Given a set of perceptions, it chooses the next action to take.
;;;;
;;;; Arguments: perceptions - the current perceptions
;;;;
;;;; Returns: an action structure defining the next action to take.
;;;;  
(defun choose-action (perceptions)
  'OVERRIDE_THIS_FUNCTION)







;;;;=======================================================================
;;;;
;;;; Critical Functions to Run the Agent
;;;;
;;;;=======================================================================


;;;; Function: main
;;;;
;;;; The main function that makes a connection to the controller
;;;; then runs the agents in the simualation.
;;;;
;;;; Arguments: host - the IP address of the controller
;;;;            port - the port of the controller
;;;;            agent_name - the name of the agent
;;;;            image_filename - the filename of the image to represent the agent
;;;;
(defun main (host port agent_name &optional image_filename)
  ;; error check the arguments
  (check-type host string "an IP address")
  (check-type port integer "an integer")
  (check-type agent_name string "a string representing the agent's name")
  (if (not (null image_filename))
      (check-type image_filename string "the filename of the image to represent the agent"))
  
  (let ((connection (socket:socket-connect port host)))
    (unwind-protect ; keep going through these steps if there's an error in one
	;; register the agent
	(write-line (register-agent-to-xml agent_name image_filename) connection)
        (force-output connection)  ; flush the output to the server
        ;; run the agent
        (run connection)
	;; close the connection
        (close connection)))

  t)



;;;; Function: run
;;;; 
;;;; Initializes the agent from the controller, then loops
;;;; repeatedly in the perception-action cycle until it
;;;; receives the final score, terminating the function.
;;;;
;;;; Arguments: connection - the connection to the controller
;;;;
(defun run (connection &aux perception (terminate nil))
  ;; initialize the map of the gridworld
  (process-initial-knowledge (read-line connection))
  (setf *current_time* 0)
  
  ;; let the agent initialize itself
  (initialize)

  ;; loop repeatedly in the perception-action cycle
  (loop until (equal terminate t) do
	(setf perception (read-command connection))
	;(format t "RAW: ~A~%" (get-word 1 perception))
	(cond ;; process the final score
	      ((string-equal (get-word 1 perception) "<finalscore>")
	       (process-result (parse-score perception))
	       (setf terminate t))
	      ;; process the perception
	      (t
	       (write-line (action-to-xml 
			    (choose-action (parse-perceptions perception))) 
			   connection)
	       (force-output connection)))
	;; increment the current time
	(incf *current_time*)))




;;;; Function: read-command
;;;;
;;;; Reads a command line from the connection, optionally discarding all but the
;;;; latest command.
;;;;
;;;; Arguments:
;;;;  connection - the connection stream
;;;;  keepOnlyLatestPerception - (Optional, default nil) when true, read-command discards
;;;;                             all commands except for the latest one
;;;;
;;;; Return:
;;;;  the command as a string
;;;;

(defun read-command (connection &optional (keepOnlyLatestPerception nil) 
				&aux (perception nil) (skippedPerceptions 0) ch line)

  ;; read once from the connection with blocking
  (loop while (> 2 (length perception)) do
	(setf perception (read-line connection)))
  ;(format t "READ PERCEPTION: ~A~%" perception)

  ;; return the perception if we only want the latest one
  (if (not keepOnlyLatestPerception)
      (return-from read-command perception))

  ;; repeatedly read from the connection as long as there is input;
  ;; doing this allows the agent to catch up (if necessary) to the
  ;; latest perceptions and ignore older ones.
  (loop while (and (peek-char-no-hang connection) 
		   (char= #\< (peek-char-no-hang connection))) do    	  ; continue reading while there's still more
        (setf line (read-line connection))
	(if (< 2 (length line))
	    (progn
	      (incf skippedPerceptions)
	      (setf perception line))))
  
  ;; notify the user if the previous step has had to discard any perceptions
  (if (not (equal 0 skippedPerceptions))
      (format t "Agent got behind ~A timesteps, so it has discarded those perceptions.~%" skippedPerceptions))

 (return-from read-command perception))


(defun peek-char-no-hang (instream)
  (and (listen instream) (peek-char t instream)))




;;;; Function: get-word
;;;;
;;;; Gets the wordIdx^th word in the given string
;;;;
;;;; Arguments: wordIdx - the index of the word to retrieve
;;;;            str     - the string
;;;;
;;;; Returns: the wordIdx^th word in str
;;;;
(defun get-word (wordIdx str &aux nextSpacePosition)
  ;; error check the arguments
  (check-type wordIdx integer "an integer >= 1")
  (check-type str string "a string")
  (assert (>= wordIdx 1)
	  (wordIdx)
	  "wordIdx must be >= 1.")
  
  ;; get the position of the next space
  (setf nextSpacePosition (position #\Space str))
  
  ;; determine the desired word
  (cond ;; no more words, so return nil
   ((equal (length str) 0) 
    nil)
   ;; it's the next word, so return it
   ((equal wordIdx 1) 
    (subseq str 0 nextSpacePosition))
   ;; there aren't any more space and it's not the next word
   ((equal nextSpacePosition nil)
    nil)
   ;; otherwise, recurse to determine the desired word
   (t
    (get-word (- wordIdx 1) 
	      (subseq str (+ nextSpacePosition 1))))))


